package pl.edu.mimuw.mas.agent.behaviour.hunting.hunter;

import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.SearchConstraints;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import pl.edu.mimuw.mas.agent.DeerAgent;
import pl.edu.mimuw.mas.agent.HunterAgent;
import pl.edu.mimuw.mas.agent.behaviour.MutableTimedBehaviour;
import pl.edu.mimuw.mas.util.DeathHelper;
import pl.edu.mimuw.mas.util.PositionHelper;
import pl.edu.mimuw.mas.util.Range;
import pl.edu.mimuw.mas.util.Time;

public class ShootBehaviour extends MutableTimedBehaviour {

	private static final long serialVersionUID = 1136059140489936472L;
	
	protected final Logger logger = LoggerFactory.getLogger(getClass());
	
	private int step = 0;

	public ShootBehaviour(HunterAgent a) {
		super(a, Time.HUNTER_SEARCH);
	}

	@Override
	protected void onTick() {
		if (!findDeer() || !isDeerShootable() || !canShoot()) {
			if (!canShoot() && step == 1) {
				// chcialem srzelac, ale ktos inny tez zglosil zadanie.
				// czekam losowa ilosc czasu
				this.setPeriod((int) (Math.random() * Time.HUNTER_SHOOT_CONFLICT_WAIT));
			}
			step = 0;
			return;
		}
		// Łoś jest widoczny i w zasięgu strzału
		switch(step) {
		case 0:
			// chce wystrzelić pocisk, więc najpierw informuję o tym wszystkich strzelców
			inform();
			step = 1;
			this.setPeriod(Time.HUNTER_SHOOT_WAIT);
			break;
		case 1:
			// wysłałem wiadomość i nikt poza mną nie zgłosił chęci strzelania 
			shoot();
			step = 0;
			break;
		}
	}
	
	private void inform() {
		HunterAgent a = (HunterAgent) myAgent;
		logger.debug("{} : rozgłaszam chęć strzału", a.getName());
		// szykuje wiadomosc
		ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
		msg.setConversationId(HunterAgent.SHOOT_INFO_CONV);
		// szukam strzelcow
		DFAgentDescription template = new DFAgentDescription();
  		ServiceDescription sd = new ServiceDescription();
  		sd.setType(HunterAgent.HUNTER_SERVICE_TYPE);
  		template.addServices(sd);
  		
  		try {
			DFAgentDescription[] results = DFService.search(a, template);
			for (DFAgentDescription result : results) {
				msg.addReceiver(result.getName());
	  		}
		} catch (FIPAException e) {
			logger.error("{} : błąd FIPA", a.getName(), e);
		}
		// wysylam
		a.send(msg);
	}
	
	private void shoot() {
		HunterAgent a = (HunterAgent) myAgent;
		logger.debug("{} : strzelam", a.getName());
		// szykuje wiadomosc
		ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
		msg.setConversationId(DeerAgent.SHOOT_CONV);
		// szukam losia
		DFAgentDescription template = new DFAgentDescription();
  		ServiceDescription sd = new ServiceDescription();
  		sd.setType(DeerAgent.SERVICE_TYPE);
  		template.addServices(sd);
  		
  		SearchConstraints sc = new SearchConstraints();
  		sc.setMaxResults(1l);
  		  		
		try {
			DFAgentDescription[] results = DFService.search(a, template, sc);
			if (results.length > 0) {
	  			msg.addReceiver(results[0].getName());
	  		}
		} catch (FIPAException e) {
			logger.error("{} : błąd FIPA", a.getName(), e);
		}
		// wysylam
		a.send(msg);
	}
	
	private boolean canShoot() {
		HunterAgent a = (HunterAgent) myAgent;
		return (a.getAID().equals(a.getLastShooter()) && step == 1) || // ostatnio to ja zglaszalem zadanie strzalu, ale jeszcze nie strzelilem
			(System.currentTimeMillis() > a.getLastShootTime() + Time.DEER_SHOOT_EFFECT); // minelo juz tyle czase, ze moge strzelac
	}
	
	/**
	 * Sprawdza czy łoś jest w zasięgu strzału oraz czy nie zostal wczesniej postrzelony.
	 * Korzysta z prawdziwej pozycji łosia korzystając z metody getDeerPosition
	 * z {@link PositionHelper}.
	 * @return prawda jeśli łoś jest w zasięgu strzału i nie został trafiony
	 */
	private boolean isDeerShootable() {
		HunterAgent a = (HunterAgent) myAgent;
		return !DeathHelper.isDeerDead() && 
			PositionHelper.getDistance(PositionHelper.getDeerPosition(), a.getPosition()) < Range.SHOOT_RANGE;  
	}
	
	/**
	 * Przeszukuje widoczny obszar w poszukiwaniu łosia
	 * @return prawda jeśli znalazł łosia
	 */
	private boolean findDeer() {
		HunterAgent a = (HunterAgent) myAgent;
		if (PositionHelper.isDeerVisible(a.getPosition())) {
			a.setDeerPosition(PositionHelper.getDeerPosition());
			return true;
		} else {
			return false;
		}
	}

}
